package internal

import (
	"context"
	"github.com/morgine/log"
	"github.com/shopspring/decimal"
	"market/internal/binance"
	"market/internal/huobi"
	"market/internal/okex"
	"strings"
	"sync"
	"time"
)

func Subscribe(coins []string, handle func(flows []*Flow)) (cancelFunc func()) {

	var instruments = make([]string, len(coins))
	var symbols = make([]string, len(coins))
	for i, coin := range coins {
		instruments[i] = strings.ToUpper(coin) + "-" + "usdt"
		symbols[i] = strings.ToLower(coin) + "usdt"
		coins[i] = strings.ToUpper(coin)
	}

	mux := NewSizeMux(coins)

	cancelOKEx := okex.SubscribeDepth5(instruments, func(data []*okex.Depth5Data) {
		mux.SetOKExData(data)
	})
	cancelHuobi := huobi.SubscribeDepth(symbols, 5, func(data *huobi.DepthData) {
		mux.SetHuobiData(data)
	})
	cancelBinance := binance.SubscribeDepth(symbols, 5, func(data *binance.DepthData) {
		mux.SetBinanceData(data)
	})

	ctx, cancel := context.WithCancel(context.Background())

	go func() {
		ticker := time.NewTicker(1 * time.Second)
		for {
			select {
			case <-ctx.Done():
				return
			case now := <-ticker.C:
				flows := mux.GetFlows(now.Unix())
				handle(flows)
			}
		}
	}()
	return func() {
		cancel()
		cancelBinance()
		cancelHuobi()
		cancelOKEx()
	}
}

type SizeMux struct {
	okexBuySize            map[string]float64
	okexSellSize           map[string]float64
	huobiBuySize           map[string]float64
	huobiSellSize          map[string]float64
	binanceBuySize         map[string]float64
	binanceSellSize        map[string]float64
	omu                    sync.Mutex
	hmu                    sync.Mutex
	bmu                    sync.Mutex
	coins                  []string
	coinSymbols            map[string]string // 火币及币安币币名称表, 如: etc => etcusdt
	coinInstruments        map[string]string // okex 币币名称表, 如: etc => ETC-usdt
	binanceLowestSellPrice map[string]string // 币安最新的最低卖价，该价格在当前时间点一定能卖出货币
	binanceHighestBuyPrice map[string]string // 币安最新的最高买价，该价格在当前时间点一定能买到货币
}

func NewSizeMux(coins []string) *SizeMux {
	return &SizeMux{
		okexBuySize:     make(map[string]float64, len(coins)),
		okexSellSize:    make(map[string]float64, len(coins)),
		huobiBuySize:    make(map[string]float64, len(coins)),
		huobiSellSize:   make(map[string]float64, len(coins)),
		binanceBuySize:  make(map[string]float64, len(coins)),
		binanceSellSize: make(map[string]float64, len(coins)),
		omu:             sync.Mutex{},
		hmu:             sync.Mutex{},
		bmu:             sync.Mutex{},
		coins:           coins,
		coinSymbols: func(coins []string) map[string]string {
			cs := make(map[string]string, len(coins))
			for _, coin := range coins {
				cs[coin] = strings.ToLower(coin) + "usdt"
			}
			return cs
		}(coins),
		coinInstruments: func(coins []string) map[string]string {
			cs := make(map[string]string, len(coins))
			for _, coin := range coins {
				cs[coin] = strings.ToUpper(coin) + "-usdt"
			}
			return cs
		}(coins),
		binanceLowestSellPrice: make(map[string]string, len(coins)),
		binanceHighestBuyPrice: make(map[string]string, len(coins)),
	}
}

type Flow struct {
	Coin            string  `json:"c"`
	BuySize         float64 `json:"b"`
	SellSize        float64 `json:"s"`
	HighestBuyPrice string  `json:"bp"` // 当前市场最高买价, 作为卖出参考
	LowestSellPrice string  `json:"sp"` // 当前市场最低卖价, 作为买进参考
	Timestamp       int64   `json:"t"`
}

func (f *Flow) GetBuyCoinPrice() (decimal.Decimal, error) {
	return decimal.NewFromString(f.LowestSellPrice)
}

func (f *Flow) GetSellCoinPrice() (decimal.Decimal, error) {
	return decimal.NewFromString(f.HighestBuyPrice)
}

func (sm *SizeMux) GetFlows(now int64) []*Flow {
	sm.omu.Lock()
	sm.hmu.Lock()
	sm.bmu.Lock()
	flows := make([]*Flow, len(sm.coins))
	for i, coin := range sm.coins {
		flow := &Flow{
			Coin:            coin,
			Timestamp:       now,
			HighestBuyPrice: sm.binanceHighestBuyPrice[sm.coinSymbols[coin]],
			LowestSellPrice: sm.binanceLowestSellPrice[sm.coinSymbols[coin]],
		}
		flow.BuySize = sm.huobiBuySize[sm.coinSymbols[coin]]
		flow.BuySize += sm.binanceBuySize[sm.coinSymbols[coin]]
		flow.BuySize += sm.okexBuySize[sm.coinInstruments[coin]]
		flow.SellSize = sm.huobiSellSize[sm.coinSymbols[coin]]
		flow.SellSize += sm.binanceSellSize[sm.coinSymbols[coin]]
		flow.SellSize += sm.okexSellSize[sm.coinInstruments[coin]]
		flows[i] = flow
		if flow.LowestSellPrice == "" {
			log.Info.Printf("%s price not exist!\n", coin)
		}
	}
	sm.omu.Unlock()
	sm.hmu.Unlock()
	sm.bmu.Unlock()
	return flows
}

func (sm *SizeMux) SetOKExData(data []*okex.Depth5Data) {
	sm.omu.Lock()
	defer sm.omu.Unlock()
	for _, datum := range data {
		var (
			sellSize float64
			buySize  float64
		)
		for _, ask := range datum.Asks {
			sellSize += ask.FloatSize()
		}
		for _, bid := range datum.Bids {
			buySize += bid.FloatSize()
		}
		sm.okexSellSize[datum.InstrumentID] = sellSize
		sm.okexBuySize[datum.InstrumentID] = buySize
	}
}

func (sm *SizeMux) SetHuobiData(data *huobi.DepthData) {
	sm.hmu.Lock()
	defer sm.hmu.Unlock()
	var (
		sellSize float64
		buySize  float64
	)
	for _, ask := range data.Asks {
		sellSize += ask.Size()
	}
	for _, bid := range data.Bids {
		buySize += bid.Size()
	}
	sm.huobiBuySize[data.Symbol] = buySize
	sm.huobiSellSize[data.Symbol] = sellSize
}

func (sm *SizeMux) SetBinanceData(data *binance.DepthData) {
	sm.bmu.Lock()
	defer sm.bmu.Unlock()
	var (
		sellSize float64
		buySize  float64
	)
	for _, ask := range data.Asks {
		sellSize += ask.FloatSize()
	}
	for _, bid := range data.Bids {
		buySize += bid.FloatSize()
	}
	if buySize > 0 {
		sm.binanceHighestBuyPrice[data.Symbol] = data.Bids[0].StringPrice()
	}
	if sellSize > 0 {
		sm.binanceLowestSellPrice[data.Symbol] = data.Asks[0].StringPrice()
	}
	sm.binanceBuySize[data.Symbol] = buySize
	sm.binanceSellSize[data.Symbol] = sellSize
}
